package database

import (
	list2 "abdis/datastruct/list"
	"abdis/interface/database"
	"abdis/interface/datastruct"
	"abdis/interface/redis"
	"abdis/lib/utils"
	"abdis/redis/protocol"
	"strconv"
)

func (db *DB) getAsList(key string) (list datastruct.List, errReply protocol.ErrorReply) {
	entity, ok := db.GetEntity(key)
	if !ok {
		return nil, nil
	}
	list, ok = entity.Data.(datastruct.List)
	if !ok {
		return nil, protocol.MakeWrongTypeErrReply()
	}
	return list, nil
}

func (db *DB) getOrInitList(key string) (list datastruct.List, isNew bool, errReply protocol.ErrorReply) {
	list, errReply = db.getAsList(key)
	if errReply != nil {
		return nil, false, errReply
	}
	isNew = false
	if list == nil {
		// TODO 先用着这个慢的，之后再换 quicklist
		list = list2.MakeLinkedList()
		db.PutEntity(key, &database.DataEntity{Data: list})
		isNew = true
	}
	return list, isNew, nil
}

func undoLPush(db *DB, args [][]byte) []database.CmdLine {
	key := string(args[0])
	count := len(args) - 1
	cmdLines := make([]database.CmdLine, 0, count)
	for i := 0; i < count; i++ {
		cmdLines = append(cmdLines, utils.ToCmdLine("LPOP", key))
	}
	return cmdLines
}

func undoLPop(db *DB, args [][]byte) []database.CmdLine {
	key := string(args[0])
	list, errReply := db.getAsList(key)
	if errReply != nil || list == nil || list.Len() == 0 {
		return nil
	}
	entity, _ := list.Get(0).([]byte)
	return []database.CmdLine{utils.ToCmdLineRaw("LPUSH", args[0], entity)}
}

func undoRPush(db *DB, args [][]byte) []database.CmdLine {
	key := string(args[0])
	count := len(args) - 1
	cmdLines := make([]database.CmdLine, 0, count)
	for i := 0; i < count; i++ {
		cmdLines = append(cmdLines, utils.ToCmdLine("RPOP", key))
	}
	return cmdLines
}

func undoRPop(db *DB, args [][]byte) []database.CmdLine {
	key := string(args[0])
	list, errReply := db.getAsList(key)
	if errReply != nil || list == nil || list.Len() == 0 {
		return nil
	}
	entity, _ := list.Get(list.Len() - 1).([]byte)
	return []database.CmdLine{utils.ToCmdLineRaw("RPUSH", args[0], entity)}
}

func undoLSet(db *DB, args [][]byte) []database.CmdLine {
	key := string(args[0])
	index, err := strconv.Atoi(string(args[1]))
	if err != nil {
		return nil
	}
	list, errReply := db.getAsList(key)
	if errReply != nil || list == nil {
		return nil
	}
	size := list.Len()
	index = utils.AdjustIndex(size, index)
	val, _ := list.Get(index).([]byte)
	return []database.CmdLine{utils.ToCmdLineRaw("LSET", args[0], args[1], val)}
}

func execLPush(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])

	list, _, errReply := db.getOrInitList(key)
	if errReply != nil {
		return errReply, false
	}

	for _, v := range args[1:] {
		list.Insert(0, v)
	}

	//db.addAof(utils.ToCmdLineRaw("LPush", args...))

	return protocol.MakeIntReply(int64(list.Len())), true
}

func execLPop(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])

	list, errReply := db.getAsList(key)
	if errReply != nil {
		return errReply, false
	}
	if list == nil {
		return protocol.MakeNullBulkReply(), false
	}

	val, _ := list.Remove(0).([]byte)
	if list.Len() == 0 {
		db.Remove(key)
	}
	return protocol.MakeBulkReply(val), true
}

func execRPush(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	list, _, errReply := db.getOrInitList(key)
	if errReply != nil {
		return errReply, false
	}

	for _, value := range args[1:] {
		list.Add(value)
	}

	return protocol.MakeIntReply(int64(list.Len())), true
}

func execRPop(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])

	list, errReply := db.getAsList(key)
	if errReply != nil {
		return errReply, false
	}
	if list == nil {
		return protocol.MakeNullBulkReply(), false
	}

	val, _ := list.RemoveLast().([]byte)
	if list.Len() == 0 {
		db.Remove(key)
	}
	return protocol.MakeBulkReply(val), true
}

func execLRange(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	begin, err1 := strconv.Atoi(string(args[1]))
	end, err2 := strconv.Atoi(string(args[2]))
	if err1 != nil || err2 != nil {
		return protocol.MakeErrReply("ERR value is invalid"), false
	}

	list, errReply := db.getAsList(key)
	if errReply != nil {
		return errReply, false
	}
	if list == nil {
		return protocol.MakeEmptyMultiBulkReply(), true
	}

	size := list.Len()
	begin, end = utils.GetListRange(size, begin, end)

	slice := list.Range(begin, end)
	ret := make([][]byte, len(slice))
	for i, raw := range slice {
		bytes, _ := raw.([]byte)
		ret[i] = bytes
	}
	return protocol.MakeMultiBulkReply(ret), true
}

func execLLen(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	list, errReply := db.getAsList(key)
	if errReply != nil {
		return errReply, false
	}
	if list == nil {
		return protocol.MakeIntReply(0), false
	}
	return protocol.MakeIntReply(int64(list.Len())), true
}

// LSET key index value
func execLSet(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	index, err := strconv.Atoi(string(args[1]))
	if err != nil {
		return protocol.MakeWrongTypeErrReply(), false
	}
	val := args[2]

	list, errReply := db.getAsList(key)
	if errReply != nil {
		return errReply, false
	}
	if list == nil {
		return protocol.MakeErrReply("ERR no such key"), false
	}

	size := list.Len()
	index = utils.AdjustIndex(size, index)

	list.Set(index, val)
	return protocol.MakeOkReply(), true
}

// LREM key count value
func execLRem(db *DB, args [][]byte) (redis.Reply, bool) {
	key := string(args[0])
	count, err := strconv.Atoi(string(args[1]))
	if err != nil {
		return protocol.MakeErrReply("ERR value is not an integer"), false
	}

	val := args[2]

	list, errReply := db.getAsList(key)
	if errReply != nil {
		return errReply, false
	}
	if list == nil {
		return protocol.MakeIntReply(0), false
	}

	removed := 0
	expected := func(a any) bool { return utils.Equals(a, val) }

	if count == 0 {
		removed = list.RemoveAllByVal(expected)
	} else if count > 0 {
		removed = list.RemoveByVal(expected, count)
	} else {
		removed = list.ReverseRemoveByVal(expected, -count)
	}

	if list.Len() == 0 {
		db.Remove(key)
	}

	return protocol.MakeIntReply(int64(removed)), true
}

func init() {
	registerCommand("LPush", execLPush, writeFirstKey, undoLPush, -3, flagWrite)
	registerCommand("LPop", execLPop, writeFirstKey, undoLPop, 2, flagWrite)
	registerCommand("RPush", execRPush, writeFirstKey, undoRPush, -3, flagWrite)
	registerCommand("RPop", execRPop, writeFirstKey, undoRPop, 2, flagWrite)
	registerCommand("LSet", execLSet, writeFirstKey, undoLSet, 4, flagWrite)
	registerCommand("LRem", execLRem, writeFirstKey, rollbackFirstKey, 4, flagWrite)

	registerCommand("LRange", execLRange, readFirstKey, nil, 4, flagReadOnly)
	registerCommand("LLen", execLLen, readFirstKey, nil, 2, flagReadOnly)
}
